﻿using System;
using System.Collections.Generic;
using MashGen.Engine.Expressions;
using System.Diagnostics;

namespace MashGen.Engine.Matching
{
    internal class Attempt
    {
        private Builder builder;

        private Stack<MatchPosition> positions;

        /// <summary>
        /// The name of the unbound variable, that has to be bound in the
        /// next match.
        /// </summary>
        private string variableToBind;

        public Attempt(Builder builder, AbstractExpression pattern, AbstractExpression root)
        {
            this.builder = builder;
            positions = new Stack<MatchPosition>();

            var variable = pattern as Variable;
            if (variable != null)
            {
                variableToBind = variable.Name;
                positions.Push(new MatchPosition());
            }
            else
            {
                positions.Push(new MatchPosition(Mode.Pattern, pattern));
            }
            Match = new Match(root);
        }

        public Match Match { get; private set; }

        public void PushPosition<T>(Func<T, AbstractExpression> accessor)
            where T : AbstractExpression
        {
            var currentPosition = positions.Peek();

            if (currentPosition.Mode == Mode.UnboundVariable)
            {
                // We are matching an unbound variable.
                // Just push a marker on the stack, so that the 
                // stack size remains correct
                positions.Push(new MatchPosition());
                return;
            }

            var nextExpression = accessor((T)currentPosition.Expression);
            if (currentPosition.Mode == Mode.Pattern && nextExpression.GetType() == typeof(Variable))
            {
                // We are matching a literal pattern and have encountered a variable.
                // Find out it if has been previously bound.
                string variableName = ((Variable)nextExpression).Name;
                AbstractExpression substitution;
                if (Match.TryGetBinding(variableName, out substitution))
                {
                    // It is bound. Start to match its content.
                    positions.Push(new MatchPosition(Mode.BoundVariable, substitution));
                }
                else
                {
                    // It is still unbound. Remember that we have to bind it 
                    // and push a marker on the stack.
                    variableToBind = variableName;
                    positions.Push(new MatchPosition());
                }
                return;
            }
            
            // We are matching a bound variable or a literal pattern. 
            // Continue to match its content.
            positions.Push(new MatchPosition(currentPosition.Mode, nextExpression));
        }

        public void MatchNumber(Number number)
        {
            if (TryBind(number))
            {
                return;
            }

            var matchNumber = positions.Peek().Expression as Number;
            if (matchNumber == null || matchNumber.Value != number.Value)
            {
                builder.AttemptFailed(this);
            }
        }

        public void MatchVariable(Variable variable)
        {
            if (TryBind(variable))
            {
                return;
            }

            var matchVariable = positions.Peek().Expression as Variable;
            if (matchVariable == null || matchVariable.Name != variable.Name)
            {
                builder.AttemptFailed(this);
            }
        }

        public void MatchStructure(AbstractExpression expression)
        {
            if (TryBind(expression))
            {
                return;
            }

            var matchExpression = positions.Peek().Expression;
            if (matchExpression.GetType() != expression.GetType())
            {
                builder.AttemptFailed(this);
            }
        }
        
        /// <summary>
        /// Binds an unbound variable to a pattern, if necessary, and finds
        /// out if the current expression needs to be matched.
        /// </summary>
        /// <param name="expression">The expression to be bound.</param>
        /// <returns><c>true</c> iff the expression has been bound.</returns>
        private bool TryBind(AbstractExpression expression)
        {
            if (variableToBind != null)
            {
                Match.AddBinding(variableToBind, expression);
                variableToBind = null;
            }
            return positions.Peek().Mode == Mode.UnboundVariable;
        }

        public void PopPosition()
        {
            positions.Pop();
            if (positions.Count == 0)
            {
                builder.AttemptSucceeded(this);
            }
        }
    }
}
